package com.google.common.io;

import java.io.*;

import com.google.common.base.Preconditions;

public class ByteStreams {
    public static ByteArrayDataOutput newDataOutput(){
        return newDataOutput(new ByteArrayOutputStream());
    }
    public static ByteArrayDataOutput newDataOutput(int size){
        if (size < 0) {
            throw new IllegalArgumentException(String.format("Invalid size: %s", size));
        }
        return newDataOutput(new ByteArrayOutputStream(size));
    }

    public static ByteArrayDataOutput newDataOutput(ByteArrayOutputStream byteArrayOutputStream) {
        return new ByteArrayDataOutputStream(Preconditions.checkNotNull(byteArrayOutputStream));
    }

    public static void readFully(InputStream in, byte[] b) throws IOException {
        readFully(in, b, 0, b.length);
    }

    public static void readFully(InputStream in, byte[] b, int off, int len) throws IOException {
        int read = read(in, b, off, len);
        if (read != len) {
            throw new EOFException(
                    "reached end of stream after reading " + read + " bytes; " + len + " bytes expected");
        }
    }

    public static int read(InputStream in, byte[] b, int off, int len) throws IOException {
        Preconditions.checkNotNull(in);
        Preconditions.checkNotNull(b);
        if (len < 0) {
            throw new IndexOutOfBoundsException(String.format("len (%s) cannot be negative", len));
        }
        Preconditions.checkPositionIndexes(off, off + len, b.length);
        int total = 0;
        while (total < len) {
            int result = in.read(b, off + total, len - total);
            if (result == -1) {
                break;
            }
            total += result;
        }
        return total;
    }

    public static byte[] toByteArray(InputStream inputStream) throws IOException {
        byte[] data = new byte[1024];
        int len;
        try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
            while ((len = inputStream.read(data)) != -1){
                outputStream.write(data, 0, len);
            }
            return outputStream.toByteArray();
        }
    }

    private static class ByteArrayDataOutputStream implements ByteArrayDataOutput {

        final DataOutput output;
        final ByteArrayOutputStream byteArrayOutputStream;

        ByteArrayDataOutputStream(ByteArrayOutputStream byteArrayOutputStream) {
            this.byteArrayOutputStream = byteArrayOutputStream;
            output = new DataOutputStream(byteArrayOutputStream);
        }

        @Override
        public void write(int b) {
            try {
                output.write(b);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void write(byte[] b) {
            try {
                output.write(b);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void write(byte[] b, int off, int len) {
            try {
                output.write(b, off, len);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeBoolean(boolean v) {
            try {
                output.writeBoolean(v);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeByte(int v) {
            try {
                output.writeByte(v);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeBytes(String s) {
            try {
                output.writeBytes(s);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeChar(int v) {
            try {
                output.writeChar(v);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeChars(String s) {
            try {
                output.writeChars(s);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeDouble(double v) {
            try {
                output.writeDouble(v);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeFloat(float v) {
            try {
                output.writeFloat(v);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeInt(int v) {
            try {
                output.writeInt(v);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeLong(long v) {
            try {
                output.writeLong(v);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeShort(int v) {
            try {
                output.writeShort(v);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public void writeUTF(String s) {
            try {
                output.writeUTF(s);
            } catch (IOException impossible) {
                throw new AssertionError(impossible);
            }
        }

        @Override
        public byte[] toByteArray() {
            return byteArrayOutputStream.toByteArray();
        }
    }

}
